﻿using System;

using RayDen.Library.Components.Color.Types;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;

using RayDen.RayEngine;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Corrector.Managers;
using RayDen.RayEngine.Data;

using RayEngine.Spectral.Entities.Materials;

namespace RayEngine.Spectral.Entities.Lighting
{
    public class TriangleMeshLight : BaseLight
    {
        private RayEngineScene scene;
        private TriangleMeshInfo mesh;
        private FastRandom rnd;
        public TriangleMeshLight(LightMaterial mt, RayEngineScene scene, TriangleMeshInfo mesh) : base(mt)
        {
            this.scene = scene;
            this.mesh = mesh;
            this.rnd = new FastRandom();

            triangleSampleData = new TriangleSample[this.mesh.TrianglesCount];

            for (int i = mesh.StartTriangle, j = 0; i < mesh.EndTriangle; i++, j++)
            {
                triangleSampleData[j] = new TriangleSample(scene.Triangles[i].AreaV(scene.Vertices), NormalModifier * scene.Triangles[i].ComputeNormal(scene.Vertices));
            }

            triangleSampleData.PartialSort((a, b) => a.Item1.CompareTo(a.Item1), 0, triangleSampleData.Length);

        }

        public override bool IsDelta()
        {
            return true;
        }

        public override IColorType Emit(ref Vector dir, out float pdf)
        {
            var tr = triangleSampleData[SampleTriangleIndex(rnd)];

            var sampleN = tr.Item2;
            float lpdf;
            pdf = 1f / triangleSampleData.Length * (1f / tr.Item1);
            if ( Normal.Dot(ref sampleN, ref dir) <= 0.0f)
                return ColorManager.Instance.Zero();
            return Material.GetEmittance(dir.x, dir.y, out lpdf);
        }

        public override void EvaluateShadow(ShadePointInfo pt, float u0, float u1, float u2, 
                                            out IColorType radiance, out float pdf,out RayData ray)
        {
            int tries = 0;

#if VERBOSE
            try
            {
#endif
            ray = new RayData() {Org = pt.HitPoint};
            Point samplePoint;
            float b0, b1, b2;
            int maxTries = 1;
        startTry:

            var index = Math.Max(0, Math.Min(triangleSampleData.Length - 1, (int)(u0 * (mesh.EndTriangle - mesh.StartTriangle))));
            Sample(ref scene.Triangles[mesh.StartTriangle + index], scene.Vertices, u2, u1, out samplePoint, out b0, out b1, out b2);

            var u = b0;
            var v = b1;

            var triangleNormal = triangleSampleData[index].Item2;
            var area = triangleSampleData[index].Item1;
            //tri.AreaV(scene.Vertices);
            //var sampleN = TriangleNormal;
            //var N = n;

            Vector wi = samplePoint - pt.HitPoint;
            //wi.Normalize();
            float distanceSquared = wi.Length2();
            var distance = MathLab.Sqrt(distanceSquared);
            wi /= distance;

            var nw = -wi;
            float sampleNdotMinusWi = Normal.Dot(ref triangleNormal, ref nw);
            float NdotWi = Normal.Dot(ref pt.GeoNormal, ref wi);
            if ((sampleNdotMinusWi <= 0f) || (NdotWi <= 0f))
            {
                tries++;
                if (tries > maxTries)
                {
                    pdf = 0f;
                    radiance = ColorManager.Instance.Zero();
                    return;
                }

                goto startTry;
            }

            pdf =
                //((1f / area) * distanceSquared / sampleNdotMinusWi) * (1f / mesh.TrianglesCount) * (1f / Math.Max(1, tries + 1));
                (distanceSquared / (sampleNdotMinusWi * area)) * 1f / mesh.TrianglesCount * (1f / Math.Max(1, tries + 1));




            // Using 0.01 instead of 0.0 to cut down fireflies
            if (pdf <= 0.01f)
            {
                pdf = 0f;
                radiance = ColorManager.Instance.Zero();

                return;
            }
            float lp;
            radiance = Material.GetEmittance(u, v, out lp);
            ray.Dir = wi;
            //, MathLab.RAY_EPSILON, distance - MathLab.RAY_EPSILON);
#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine(ex.Message + ex.StackTrace);
                Tracer.TraceLine("Triangle data offset " + (mesh.StartTriangle).ToString() + "of " + triangleSampleData.Length);
                throw ex;
            }
#endif
        }

        public override void EvaluateIllumination(RayEngineScene scn, float u0, float u1, float u2, float u3, float u4, 
                                                  out RayData ray,out float pdf, out IColorType radiance)
        {
            throw new System.NotImplementedException();
        }

        #region Internals

        struct TriangleSample
        {
            public Normal Item2;
            public float Item1;

            public TriangleSample(float i1, Normal i2)
            {
                Item1 = i1;
                Item2 = i2;
            }
        }

        public const float NormalModifier = -1.0f;
        public string LightName;

        private TriangleSample[] triangleSampleData;

        protected TriangleDataInfo SampleTriangle(float f, out int index)
        {
            index = Math.Max(0, Math.Min(triangleSampleData.Length - 1, (int)(f * (mesh.EndTriangle - mesh.StartTriangle))));

            return scene.Triangles[mesh.StartTriangle + index];
        }

        private int SampleTriangleIndex(FastRandom rnd)
        {
            return rnd.Next(0, triangleSampleData.Length - 1);
        }

        public static void Sample(ref TriangleDataInfo tri, Point[] meshVertices, float u0, float u1, out Point samplePoint, out float b0, out float b1, out float b2)
        {
            b0 = 0f;
            b1 = 0f;
            MC.UniformSampleTriangle(u0, u1, ref  b0, ref b1);

            var p0 = meshVertices[tri.v0.VertexIndex];
            var p1 = meshVertices[tri.v1.VertexIndex] - p0;
            var p2 = meshVertices[tri.v2.VertexIndex] - p0;
            b2 = 1f - (b0) - (b1);


            samplePoint = new Point(
                ((b0) * p0.x + (b1) * p1.x + (b2) * p2.x),
                ((b0) * p0.y + (b1) * p1.y + (b2) * p2.y),
                ((b0) * p0.z + (b1) * p1.z + (b2) * p2.z));
        }

        #endregion
    }
}